<?php

namespace System\Database;

if (!defined('BASEPATH')) die('Access Denied.');

/**
 * 
 * µMVC Framework (c) 2011.
 * 
 * PHP5 MVC Framework software for PHP 5.2.17 or later
 * 
 * Database adapted from Obullo framework based on work from CodeIgniter
 * 
 * @package         µMVC
 * @filename        system/database/db.class.php
 * @copyright       Lanz (c) 2011.
 * 
 * @author          Ersin Güvenç
 * 
 */

use \PDO as PDO;
use \PDOStatement as PDOStatement;

class DBException extends \System\Core\CommonException {}

/**
 * DB class
 * 
 * @package         µMVC
 * @subpackage      Database
 * @category        System
 */
//---------- DB Clases load schema ----------//
//  
// include DBAC_Adapter.php
// 
// DBAC_Adapter::AC($active_record = true); 
//      o include DBResults.php
//      o include DBac_record.php
//      o include DBac_sw_on.php
// DBAC_Adapter::AC($active_record = false);
//      o include DBResults.php
//      o include DBac_sw_off.php
// include DB.php
// include DBFactory.php
class DB extends DBActiveRecord
{

    /**
     * prepare switch
     * 
     * @var boolean
     */
    public $prepare = FALSE;

    /**
     * Prepare options
     * 
     * @var mixed
     */
    public $p_opt = array();

    /**
     * Store last queried sql
     * 
     * @var string
     */
    public $last_sql = '';

    /**
     * Store last PDO execute 
     * values
     * 
     * @var array
     */
    private $last_values;

    /**
     * Parent Query - PDOStatement Object
     * 
     * @var object
     */
    private $PQ = '';

    /**
     * Count execute func.
     * 
     * @var int
     */
    public $exec_count = 0;
    // Brackets for FROM portion..
    public $left = '';
    public $right = '';
    
    // Store Curent PDO driver
    public $db_driver = '';

    function __construct($dsn, $user = NULL, $pass = NULL, $options = NULL)
    {
        parent::__construct($dsn, $user, $pass, $options);

        //throw new DBException('SQLSTATE: test error!');
    }

    // Set db_driver
    function set_driver($db_driver)
    {
        $this->db_driver = $db_driver;

        // factory for sql differences..
        switch ($db_driver)
        {
            case 'MYSQL':
                // FROM portions brackets...
                $this->left = '(';
                $this->right = ')';
                break;

            case 'ODBC':
                $this->left = '(';
                $this->right = ')';
                break;

            case 'SQLITE':
                $this->left = '(';
                $this->right = ')';
                break;
        }
    }

    // get current db driver.
    public function get_driver()
    {
        return $this->db_driver;
    }

    // PDO prepare function.
    public function prep($options = array())
    {
        $this->p_opt = &$options;
        $this->prepare = TRUE;
    }

    /**
     * Flexible Prepared or Direct Query
     *         
     * @author  Ersin Güvenç
     * @param   string $sql
     * @version 1.0
     * @return  PDOStatement
     */
    public function query($sql = NULL)
    {
        $this->last_sql = $sql;

        if ($this->prepare)
        {
            $this->PQ = parent::prepare($sql, $this->p_opt);
            return NULL;
        }

        $this->PQ = parent::query($sql);

        return $this;
    }

    // escape like
    private static function escape_like($str)
    {
        $str = str_replace(array('%', '_'), array('\\%', '\\_'), $str);
        return $str;
    }

    /**
     * Execute prepared query
     * 
     * @author   Ersin Güvenç
     * @author   you..
     * @version  0.1
     * @version  0.2 added secure like conditions support
     * @param    array $array bindValue or bindParam arrays
     * @param    boolean $bindParam switch Default bindValue 
     * @return   void | NULL 
     */
    public function exec($array = NULL, $bindParam = FALSE)
    {
        if ($this->last_sql != NULL AND $this->exec_count == 0)
            $this->query($this->last_sql);

        // if(class_exists('OB_DB_active_record'))
        // this is a security reason, if developer use like operator,
        // add % % operators inside of exec func.
        if (count($this->ar_like) > 0)
        {
            $new_array = $array;
            $array = array();
            foreach ($new_array as $key => $val)
            {
                if (strpos($val, 'both|') === 0)
                {
                    $val = substr(self::escape_like($val), 5);
                    $val = '%' . "{$val}" . '%';
                }

                if (strpos($val, 'before|') === 0)
                {
                    $val = substr(self::escape_like($val), 7);
                    $val = '%' . "{$val}";
                }

                if (strpos($val, 'after|') === 0)
                {
                    $val = substr(self::escape_like($val), 6);
                    $val = "{$val}" . '%';
                }

                $array[$key] = $val;
            }
        }

        //print_r($array); exit;

        if (is_array($array))
        {
            if (!self::isAssoc($array))
                throw new DBException('PDO binds array must be associative !');

            switch ($bindParam)
            {
                case TRUE:
                    $this->_bindParams($array);
                    $this->last_values = $array;
                    break;

                default:
                    $this->_bindValues($array);
                    $this->last_values = $array;
            }
        }

        // if no query builded by active record
        // switch to pdo::statement
        $this->PQ->execute();

        // reset prepare variable 
        $this->prepare = FALSE;

        ++$this->exec_count;

        return NULL;
    }

    /**
     * Exec just for INSERT and 
     * UPDATE operations it returns to
     * number of affected rows after the write
     * operations.
     * 
     * @author   Ersin Güvenç
     * @author   you..
     * @param    string $sql
     * @version  0.1
     * @return   boolean
     */
    protected function _exec($sql)
    {
        $this->last_sql = $sql;

        return parent::exec($sql);
    }

    // automatically secure bind values..
    private function _bindValues($array)
    {
        foreach ($array as $key => $val)
        {
            switch (gettype($val))
            {
                case 'string':
                    //echo 'string'; 
                    $this->bval($key, $val, PDO::PARAM_STR);
                    break;

                case 'integer':
                    $this->bval($key, $val, PDO::PARAM_INT);
                    break;

                case 'boolean':
                    //echo 'BOOL';
                    $this->bval($key, $val, PDO::PARAM_BOOL);
                    break;

                case 'null':
                    $this->bval($key, $val, PDO::PARAM_NULL);
                    break;

                default:
                    $this->bval($key, $val, PDO::PARAM_STR);
            }
        }
    }

    // automatically secure bind params..
    private function _bindParams($array)
    {
        foreach ($array as $key => $val)
        {
            switch (gettype($val))
            {
                case 'string':
                    $this->bparam($key, $val, PDO::PARAM_STR);
                    break;

                case 'integer':
                    $this->bparam($key, $val, PDO::PARAM_INT);
                    break;

                case 'boolean':
                    $this->bparam($key, $val, PDO::PARAM_BOOL);
                    break;

                case 'null':
                    $this->bparam($key, $val, PDO::PARAM_NULL);
                    break;

                default:
                    $this->bparam($key, $val, PDO::PARAM_STR);
            }
        }
    }

    /**
     * Fetch prepared or none prepared last_query
     * 
     * @author  Ersin Güvenç
     * @version 0.1
     * @version 0.2 added prepared param
     * @param   boolean $prepared
     * @return  string
     */
    public function last_query($prepared = FALSE)
    {
        if ($prepared)
        {
            $vals = $this->last_values;

            $quote_added_vals = array();
            foreach (array_values($vals) as $q)
                $quote_added_vals[] = "'" . $q . "'";

            return str_replace(array_keys($vals), $quote_added_vals, $this->last_sql);
        }

        return $this->last_sql;
    }

    // get PDO::lastInsertId()
    public function insert_id()
    {
        return parent::lastInsertId();
    }

    // Alias of PDO_Statement::bindValue()
    public function bval($param, $val, $type)
    {
        $this->PQ->bindValue($param, $val, DBResults::type($type));
    }

    // Alias of PDO_Statement::bindParam()
    public function bparam($param, $val, $type, $length = NULL, $driver_options = NULL)
    {
        $this->PQ->bindParam($param, $val, DBResults::type($type), $length, $driver_options);
    }

    // Get available drivers on your host
    public function drivers()
    {
        return PDO::getAvailableDrivers();
    }

    // Get results as associative array
    public function assoc()
    {
        return $this->PQ->fetch(PDO::FETCH_ASSOC);
    }

    // Get result as object
    public function obj()
    {
        return $this->PQ->fetch(PDO::FETCH_OBJ);
    }

    // Same as object
    public function row()
    {
        return $this->PQ->fetch(PDO::FETCH_OBJ);
    }

    // Get all results by assoc, object or what u want
    public function all($type = NULL)
    {
        return $this->PQ->fetchAll(DBResults::fetch($type));
    }

    // Get column numbers, results in assoc
    public function num()
    {
        return $this->PQ->fetch(PDO::FETCH_NUM);
    }

    // Number of rows
    public function num_rows()
    {
        return $this->PQ->rowCount();
    }

    // Get column names and numbers (both)
    public function both()
    {
        return $this->PQ->fetch(PDO::FETCH_BOTH);
    }

    // check array associative or not 
    private static function isAssoc($arr)
    {
        return array_keys($arr) !== range(0, count($arr) - 1);
    }

}

//end class.
?>
